package org.loadsavant.task.parse;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.loadsavant.annotations.Task;
import org.loadsavant.annotations.TaskAttribute;
import org.loadsavant.task.TaskAttributeDefinition;
import org.loadsavant.task.TaskDefinition;
import org.objectweb.asm.ClassReader;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;


public class AsmTaskDefinitionParser implements TaskDefinitionParser {
	private static final Log log = LogFactory.getLog(AsmTaskDefinitionParser.class);
	
	private ClassLoader classLoader;
	
	public AsmTaskDefinitionParser(ClassLoader classLoader) {
		this.classLoader = classLoader;
	}
	
	public TaskDefinition createTaskDefinition(InputStream is) throws IOException {
		if (is == null) {
			throw new IllegalArgumentException("Input stream must not be null");
		}
		
		ClassReader reader = new ClassReader(is);
		
		AsmTaskVisitor visitor = new AsmTaskVisitor();
		reader.accept(visitor, true);
		
		if (visitor.isValidTask()) {			
			String className = visitor.getClassName();
			log.debug("Found task class " + className);
			try {
				Class<?> taskClass = classLoader.loadClass(className);
				
				Task taskAnnotation =
					taskClass.getAnnotation(TASK_ANNOTATION_TYPE);	
				
				Set<TaskAttributeDefinition> attributes =
					getAttributeDefinitions(taskClass);
				
				return new TaskDefinition(taskClass, taskAnnotation.name(), 
						taskAnnotation.description(), taskAnnotation.background(), 
						attributes);
			} catch (ClassNotFoundException e) {
				log.warn("Unable to load class " + className, e);
			}
		}
		
		return null;
	}
	
	private Set<TaskAttributeDefinition> getAttributeDefinitions(Class<?> clazz) {
		final Set<TaskAttributeDefinition> attributes = 
			new HashSet<TaskAttributeDefinition>();
		
		ReflectionUtils.doWithFields(clazz, new FieldCallback() {
			public void doWith(Field field) throws IllegalArgumentException,
					IllegalAccessException {
				
				TaskAttribute annotation = 
					field.getAnnotation(TASK_ATTRIBUTE_ANNOTATION_TYPE);
				
				if (annotation != null) {
					String name = annotation.name().equals("") ? field.getName() :
						annotation.name();
					
					attributes.add(new TaskAttributeDefinition(field,
							name, annotation.description(), annotation.required()));	
				}							
			}			
		});
		
		return attributes;
	}
}
